/*
-------------------------------------------------------------------
BIE is Copyright 2001-2003 Brunswick Corp.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/bie_license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
*/
package com.webdeninteractive.sbie.util;

import java.util.Enumeration;
import java.lang.ClassLoader;
import java.net.URLClassLoader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.net.URL;
import java.io.InputStream;
import java.net.MalformedURLException;

/**
 * A ClassLoader to which JAR files may be added at runtime.
 * <p>
 * JarPitClassLoader will first attempt to load a requested class with
 * the system ClassLoader (or, alternatively, a designated parent
 * ClassLoader passed to the constructor), and if that fails will look
 * in its registered classpath.
 * <p>
 * JAR files may be added to the search set when the JarPitClassLoader
 * is created, and new JARs may be added with addJar() at any time
 * thereafter.
 * <p>
 * This class is implemented as a facade for a "stack" of URLClassLoaders,
 * using the parent-child deferral strategy of Java 1.2 ClassLoaders.
 * Adding JARs at runtime adds new URLClassLoaders to the stack.
 *
 * @see java.lang.ClassLoader
 * @see java.net.URLClassLoader
 *
 * @author Greg Fast <gfast@wdimail.com>
 * @version $Id: JarPitClassLoader.java,v 1.2 2003/05/27 22:07:56 gdf Exp $
 *
 */
public class JarPitClassLoader extends ClassLoader {

    /** This is the ClassLoader to which methods are deferred.
     *  It is manipulated at instantiation, and by addJar().
     */
    protected ClassLoader wrappedClassLoader;


    /** Creates a new JarPitClassLoader which defaults to using the
     *  system ClassLoader.
     */
    public JarPitClassLoader() throws IOException {
        super( ClassLoader.getSystemClassLoader() );
        init( new File[0] ); // init with empty array
    }


    /** Creates a new JarPitClassLoader with an initial class path
     *  containing any JAR files in the given directory.
     *  This will defer to the system ClassLoader if possible.
     * @param initDirectorie Directory containing 0 or more JAR files.
     * @throws IOException if initDirectorie is not a directory, 
     *                     or if the JARs in it cannot be read.
     */
    public JarPitClassLoader( File initDirectorie ) throws IOException {
        super( ClassLoader.getSystemClassLoader() );
        File[] iFiles = { initDirectorie };
        init( iFiles );
    }


    /** Creates a new JarPitClassLoader with an initial class path
     *  containing any JAR files found in the given array of directories
     *  (perhaps none).
     *  This will defer to the system ClassLoader if possible.
     * @param initDirectories Array of directories containing JAR files.
     * @throws IOException if any of the given File objects are not
     *                     directories, or if any JAR in them cannot be read.
     */
    public JarPitClassLoader( File[] initDirectories ) throws IOException {
        super( ClassLoader.getSystemClassLoader() );
        init( initDirectories );
    }


    /** Creates a new JarPitClassLoader with an initial class path
     *  containing any JAR files found in the given array of directories,
     *  and whose parent is the given ClassLoader (rather than the system
     *  ClassLoader).
     * @param initDirectories Array of directories containing JAR files.
     * @throws IOException if any of the given File objects are not
     *                     directories, or if any JAR in them cannot be read.
     */
    public JarPitClassLoader( File[] initDirectories, ClassLoader parent )
        throws IOException 
    {
        super( parent );
        init( initDirectories );
    }

    
    /** Internal method to initialize this loader, called by constructors.
     *  Looks through each given directory, keeping track of any JARs it finds
     *  (by the ".jar" extension), and then creates a URLClassLoader
     *  pointing at the found list of JARs.
     *  (The length of the directory list may be zero, and the number of
     *  JARs found may also be zero.)
     * @throws IOException if any of initDirectories is not a directory, 
     *                     or if any read errors occur.
     */
    private void init( File[] initDirectories )
        throws IOException
    {
        ArrayList cp = new ArrayList( initDirectories.length );
        for( int i=0; i<initDirectories.length; i++ ) {
            if ( ! initDirectories[i].isDirectory() ) {
                throw new IOException("Not a directory: "+initDirectories[i]);
            }
            File[] jarfiles = initDirectories[i].listFiles(
                new FilenameFilter() {
                    public boolean accept( File dir, String name ) {
                        return (name.endsWith(".jar") || name.endsWith(".zip"));
                    }
                } );
            for( int j=0; j<jarfiles.length; j++ ) {
                /* not in 1.3.1:
                cp.add( jarfiles[j].toURI().toURL() );
                */
                cp.add( jarfiles[j].toURL() );
            }
        } // foreach initDirectories
        URL[] jarurls = (URL[])cp.toArray( new URL[0] );
        /* We keep this class's parent, as the "root" of the CL stack,
         * so that deferral works as expected. */
        wrappedClassLoader = new URLClassLoader( jarurls, this.getParent() );
    
    } // init()


    /** Add a new JAR file to the set this ClassLoader knows about.
     *  (The param here is a jar file, not a directory like the constructor.)
     *  @param jarfile File pointing to new JAR file to use.
     *  @throws IOException if jarfile is invalid.
     */
    public void addJar( File jarfile ) throws IOException {
        /* Create a new URLClassLoader, which points to the given
         * jar and which defers to the current wrappedClassLoader,
         * then make it the wrapped loader.
         */
        /* not in 1.3.1:
        URL[] jarurls = { jarfile.toURI().toURL() };
        */
        URL[] jarurls = { jarfile.toURL() };
        URLClassLoader newloader = 
            new URLClassLoader( jarurls, wrappedClassLoader );
        wrappedClassLoader = newloader;
        
    } // addJar()


    /* Defer standard CL calls on to wrappedClassLoader: */

    /** See ClassLoader */
    public Class loadClass(String name) throws ClassNotFoundException {
        return wrappedClassLoader.loadClass(name);
    }

    /** See ClassLoader */
    public URL getResource(String name) {
        return wrappedClassLoader.getResource(name);
    }

    /** See ClassLoader */
    public InputStream getResourceAsStream(String name) {
        return wrappedClassLoader.getResourceAsStream(name);
    }

//1.3.1: 
//1.3.1:     /** See ClassLoader */
//1.3.1:     public void clearAssertionStatus() {
//1.3.1:         wrappedClassLoader.clearAssertionStatus();
//1.3.1:     }
//1.3.1: 
//1.3.1:     /** See ClassLoader */
//1.3.1:     public void setClassAssertionStatus( String className, boolean enabled ) {
//1.3.1:         wrappedClassLoader.setClassAssertionStatus( className, enabled );
//1.3.1:     }
//1.3.1: 
//1.3.1:     /** See ClassLoader */
//1.3.1:     public void setDefaultAssertionStatus(boolean enabled) {
//1.3.1:         wrappedClassLoader.setDefaultAssertionStatus(enabled);
//1.3.1:     }
//1.3.1: 
//1.3.1:     /** See ClassLoader */
//1.3.1:     public void setPackageAssertionStatus(String packageName, 
//1.3.1:                                           boolean enabled) 
//1.3.1:     {
//1.3.1:         wrappedClassLoader.setPackageAssertionStatus( packageName, enabled );
//1.3.1:     }
    
    

    // It would be cool to queue added jars here, and 
    // add them by batch when the next loadClass() is called,
    // (which would avoid getting a new URLClassLoader each time 
    // this method is called)

}
